/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.iotdb.tsfile.utils;

import java.util.ArrayList;
import java.util.List;

/**
 * this class is used to concatenate Strings effectively.It contains a StringBuilder and initialize
 * it until {@code toString} is called. Note:it's not thread safety
 */
public class StringContainer {

    // while call toString, all substrings are jointed with joinSeparator
    private final String joinSeparator;
    private StringBuilder stringBuilder;
    private ArrayList<String> sequenceList;
    private ArrayList<String> reverseList;
    /**
     * the summation length of all string segments.
     */
    private int totalLength = 0;
    /**
     * the count of string segments.
     */
    private int count = 0;

    private boolean isUpdated = true;
    private String cache;

    /**
     * defines the constructor function for the class.
     */
    public StringContainer() {
        sequenceList = new ArrayList<>();
        reverseList = new ArrayList<>();
        joinSeparator = null;
    }

    /**
     * defines the constructor function for the class.
     */
    public StringContainer(String joinSeparator) {
        sequenceList = new ArrayList<>();
        reverseList = new ArrayList<>();
        this.joinSeparator = joinSeparator;
    }

    public StringContainer(String[] strings) {
        this();
        addTail(strings);
    }

    public StringContainer(String[] strings, String joinSeparator) {
        this(joinSeparator);
        addTail(strings);
    }

    public int size() {
        return count;
    }

    public int length() {
        return totalLength;
    }

    public List<String> getSequenceList() {
        return sequenceList;
    }

    public List<String> getReverseList() {
        return reverseList;
    }

    /**
     * add a objects array at this container's tail.
     *
     * @param objs -to be added
     * @return another string contains objs as tail
     */
    public StringContainer addTail(Object... objs) {
        isUpdated = true;
        count += objs.length;
        for (int i = 0; i < objs.length; i++) {
            String str = objs[i].toString();
            totalLength += str.length();
            sequenceList.add(str);
        }
        return this;
    }

    /**
     * add a Strings array at this container's tail.<br>
     * strings:"a","b","c",<br>
     * StringContainer this:["d","e","f"],<br>
     * result:this:["d","e","f","a","b","c"],<br>
     *
     * @param strings - to be added
     * @return - this object
     */
    public StringContainer addTail(String... strings) {
        isUpdated = true;
        count += strings.length;
        for (int i = 0; i < strings.length; i++) {
            totalLength += strings[i].length();
            sequenceList.add(strings[i]);
        }
        return this;
    }

    /**
     * add a StringContainer at this container's tail.<br>
     * param StringContainer:["a","b","c"],<br>
     * this StringContainer :["d","e","f"],<br>
     * result:this:["d","e","f","a","b","c"],<br>
     *
     * @param myContainer - to be added
     * @return - this object
     */
    public StringContainer addTail(StringContainer myContainer) {
        isUpdated = true;
        List<String> mySeqList = myContainer.getSequenceList();
        List<String> myRevList = myContainer.getReverseList();
        count += myRevList.size() + mySeqList.size();
        String temp;
        for (int i = myRevList.size() - 1; i >= 0; i--) {
            temp = myRevList.get(i);
            sequenceList.add(temp);
            totalLength += temp.length();
        }
        for (int i = 0; i < mySeqList.size(); i++) {
            temp = mySeqList.get(i);
            sequenceList.add(temp);
            totalLength += temp.length();
        }
        return this;
    }

    /**
     * add a Strings array from this container's header.<br>
     * strings:"a","b","c",<br>
     * StringContainer this:["d","e","f"],<br>
     * result:this:["a","b","c","d","e","f"],<br>
     *
     * @param strings - to be added
     * @return - this object
     */
    public StringContainer addHead(String... strings) {
        isUpdated = true;
        count += strings.length;
        for (int i = strings.length - 1; i >= 0; i--) {
            totalLength += strings[i].length();
            reverseList.add(strings[i]);
        }
        return this;
    }

    /**
     * add a StringContainer from this container's header.<br>
     * StringContainer m:["a","b","c"],<br>
     * StringContainer this:["d","e","f"],<br>
     * result:this:["a","b","c","d","e","f"],<br>
     *
     * @param myContainer - given StringContainer to be add in head
     * @return - this object
     */
    public StringContainer addHead(StringContainer myContainer) {
        isUpdated = true;
        List<String> mySeqList = myContainer.getSequenceList();
        List<String> myRevList = myContainer.getReverseList();
        count += myRevList.size() + mySeqList.size();
        String temp;
        for (int i = mySeqList.size() - 1; i >= 0; i--) {
            temp = mySeqList.get(i);
            reverseList.add(temp);
            totalLength += temp.length();
        }
        for (int i = 0; i < myRevList.size(); i++) {
            temp = myRevList.get(i);
            reverseList.add(temp);
            totalLength += temp.length();
        }
        return this;
    }

    @Override
    public String toString() {
        if (!isUpdated) {
            return cache;
        }
        if (totalLength <= 0) {
            return "";
        }
        if (joinSeparator == null) {
            stringBuilder = new StringBuilder(totalLength);
            for (int i = reverseList.size() - 1; i >= 0; i--) {
                stringBuilder.append(reverseList.get(i));
            }
            for (int i = 0; i < sequenceList.size(); i++) {
                stringBuilder.append(sequenceList.get(i));
            }
            cache = stringBuilder.toString();
        } else {
            cache = join(joinSeparator);
        }
        isUpdated = false;
        return cache;
    }

    /**
     * for all string in rev and seq, concat them with separator and return String.
     *
     * @param separator separator of string
     * @return - result joined in type of String with parameter
     */
    public String join(String separator) {
        if (totalLength <= 0) {
            return "";
        }
        stringBuilder = new StringBuilder(totalLength + (count - 1) * separator.length());
        for (int i = reverseList.size() - 1; i >= 1; i--) {
            stringBuilder.append(reverseList.get(i));
            stringBuilder.append(separator);
        }
        if (!reverseList.isEmpty()) {
            stringBuilder.append(reverseList.get(0));
            if (!sequenceList.isEmpty()) {
                stringBuilder.append(separator);
            }
        }
        int i;
        for (i = 0; i < sequenceList.size() - 1; i++) {
            stringBuilder.append(sequenceList.get(i));
            stringBuilder.append(separator);
        }
        if (!sequenceList.isEmpty()) {
            stringBuilder.append(sequenceList.get(i));
        }
        return stringBuilder.toString();
    }

    /**
     * return a sub-string in this container.<br>
     * e.g. this container is ["aa","bbb","cc","d","ee"]; this.getSubString(0) = "a";
     * this.getSubString(2) ="c";this.getSubString(-1) = "ee";
     *
     * @param index - the index of wanted sub-string
     * @return - substring result
     */
    public String getSubString(int index) {
        int realIndex = index >= 0 ? index : count + index;
        if (realIndex < 0 || realIndex >= count) {
            throw new IndexOutOfBoundsException(
                    String.format("Index: %d, Real Index: %d, Size: %d", index, realIndex, count));
        }
        if (realIndex < reverseList.size()) {
            return reverseList.get(reverseList.size() - 1 - realIndex);
        } else {
            return sequenceList.get(realIndex - reverseList.size());
        }
    }

    /**
     * /** return a sub-container consist of several continuous strings in this {@code container.If
     * start <= end, return a empty container} e.g. this container is ["aa","bbb","cc","d","ee"];
     * this.getSubString(0,0) = ["aa"]<br>
     * this.getSubString(1,3) = ["bbb","cc","d"]<br>
     * this.getSubString(1,-1) = ["bbb","cc","d", "ee"]<br>
     *
     * @param start - the start index of wanted sub-string
     * @param end   - the end index of wanted sub-string
     * @return - substring result
     */
    public StringContainer getSubStringContainer(int start, int end) {
        int realStartIndex = start >= 0 ? start : count + start;
        int realEndIndex = end >= 0 ? end : count + end;
        if (realStartIndex < 0 || realStartIndex >= count) {
            throw new IndexOutOfBoundsException(
                    String.format(
                            "start Index: %d, Real start Index: %d, Size: %d", start, realStartIndex, count));
        }
        if (realEndIndex < 0 || realEndIndex >= count) {
            throw new IndexOutOfBoundsException(
                    String.format("end Index: %d, Real end Index: %d, Size: %d", end, realEndIndex, count));
        }
        StringContainer ret = new StringContainer(joinSeparator);
        if (realStartIndex < reverseList.size()) {
            for (int i = reverseList.size() - 1 - realStartIndex;
                 i >= Math.max(0, reverseList.size() - 1 - realEndIndex);
                 i--) {
                ret.addTail(this.reverseList.get(i));
            }
        }
        if (realEndIndex >= reverseList.size()) {
            for (int i = Math.max(0, realStartIndex - reverseList.size());
                 i <= realEndIndex - reverseList.size();
                 i++) {
                ret.addTail(this.sequenceList.get(i));
            }
        }
        return ret;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        if (joinSeparator != null) {
            result = prime * result + joinSeparator.hashCode();
        }
        for (String string : reverseList) {
            result = prime * result + ((string == null) ? 0 : string.hashCode());
        }
        for (String string : sequenceList) {
            result = prime * result + ((string == null) ? 0 : string.hashCode());
        }
        return result;
    }

    @Override
    public boolean equals(Object sc) {
        if (sc == null) {
            return false;
        }
        if (this.getClass() != sc.getClass()) {
            return false;
        }
        return this.equals((StringContainer) sc);
    }

    /**
     * judge whether the param is equal to this container.
     *
     * @param sc -StringContainer Object to judge whether the object is equal to this container
     * @return boolean value to judge whether is equal
     */
    public boolean equals(StringContainer sc) {
        if (sc == this) {
            return true;
        }
        if (count != sc.count) {
            return false;
        }
        if (totalLength != sc.totalLength) {
            return false;
        }
        if (!joinSeparator.equals(sc.joinSeparator)) {
            return false;
        }
        if (sequenceList.size() != sc.sequenceList.size()) {
            return false;
        }
        for (int i = 0; i < sequenceList.size(); i++) {
            if (!sequenceList.get(i).equals(sc.sequenceList.get(i))) {
                return false;
            }
        }
        if (reverseList.size() != sc.reverseList.size()) {
            return false;
        }
        for (int i = 0; i < reverseList.size(); i++) {
            if (!reverseList.get(i).equals(sc.reverseList.get(i))) {
                return false;
            }
        }
        return true;
    }

    @Override
    public StringContainer clone() {
        StringContainer ret = new StringContainer(joinSeparator);
        for (String s : sequenceList) {
            ret.sequenceList.add(s);
        }
        for (String s : reverseList) {
            ret.reverseList.add(s);
        }
        ret.totalLength = totalLength;
        ret.count = count;
        ret.isUpdated = isUpdated;
        ret.cache = cache;
        return ret;
    }
}
